package cn.gs.meta.ops;

// @author gs
import cn.gs.common.meta.OgnShow;
import cn.gs.meta.db.tables.Keep;
import cn.gs.meta.db.tables.ModelBase;
import cn.gs.meta.db.tables.Obj;
import cn.gs.meta.db.tables.Ogn;
import cn.gs.util.Eval;
import static cn.gs.util.Eval.*;
import cn.gs.util.HibernateUtil;
import cn.gs.util.Pair;
import java.util.Map.Entry;
import java.util.*;

public class OgnOps {

    private OgnKeepOps ops = new OgnKeepOps();

    public boolean reset() {
        Map<ModelBase, Double> chg = new HashMap<>();
        Map<ModelBase, Double> use = new HashMap<>();
        for (Entry<ModelBase, Keep> e : ops.getOgn().getKeeps().entrySet()) {
            if (Math.abs(e.getValue().getCnt()) > 1e-10) {
                chg.put(e.getKey(), e.getValue().getCnt());
            }
            if (Math.abs(e.getValue().getUsed()) > 1e-10) {
                use.put(e.getKey(), e.getValue().getUsed());
            }
        }
        chg = addMap(chg, 0, 1, false, true, false, false);
        use = addMap(use, 0, 1, false, true, false, true);
        return (equal(chg, null) && equal(use, null));
    }

    public boolean addUsed(ModelBase model, double count, boolean force, boolean alterable) {
        boolean b = isZero(
                add(model, 0, count, force, alterable, true, true));
        HibernateUtil.getSession().save(ops.getOgn());
        return b;
    }

    public boolean addCount(ModelBase model, double count, double total, boolean force, boolean alterable) {
        if (Math.abs(count) < 1e-10) {
            return true;
        }
        Keep keep = ops.peek(model);
        double x = keep.getCnt();
        boolean b = isZero(Math.abs(add(model, 0, count, force, alterable, true, false)));
        if (b) {
            if (isZero(x)) {
                keep.setPerPrice(total / count);
            }
            double y = keep.getCnt();
            if (Math.abs(keep.getCnt() + count) > 1e-10) {
                keep.setPerPrice((total * (y - x) / count + keep.getPerPrice() * x) / y);
            }
            HibernateUtil.getSession().save(ops.getOgn());
        }
        return b;
    }

    public boolean addCount(ModelBase model, double count, boolean force, boolean alterable) {
        return Eval.isZero(add(model, 0, count, force, alterable, true, false));
    }

    /**
     * @return 返回没有成功的数目
     */
    Map<ModelBase, Double> addMap(Map<ModelBase, Double> map, int rank, double count,
            boolean force, boolean alterable, boolean renew, boolean isUse) {
        Map<ModelBase, Double> ret = new HashMap<>();
        if (0 <= rank && rank <= 5 && map != null) {
            for (Entry<ModelBase, Double> e : map.entrySet()) {
                double d = add(e.getKey(), rank, count * e.getValue(), force, alterable, renew, isUse);
                if (Math.abs(d) > 1e-10) {
                    ret.put(e.getKey(), d);
                }
            }
        }
        return ret;
    }

    double addMapOneReturn(Map<ModelBase, Double> map, int rank, double count,
            boolean force, boolean alterable, boolean renew, boolean isUse) {
        if (isZero(count)) {
            return 0;
        }
        double d = 0;
        Map<ModelBase, Double> ret = addMap(map, rank, count, force, alterable, renew, isUse);
        for (Entry<ModelBase, Double> e : ret.entrySet()) {
            Double c = map.get(e.getKey());
            if (c == null || c == 0) {
                d = count;
                break;
            } else {
                c = e.getValue() / c;
                d = Eval.absMax(c, d);
            }
        }
        return d;
    }

    /**
     * 剩余的数目
     *
     * @param model
     * @param ret Pair<Map<某个替换项, 被替换的项的数目>, 最后的替换项>
     * @param count
     * @return
     */
    double needRep(List<Pair<ModelBase, Double>> ret, Pair<ModelBase, Double> rat, ModelBase model, int rank, double count, boolean renew, boolean isuse) {
        if (isZero(count)) {
            return 0;
        }
        List<Pair<ModelBase, Double>> list = findReplace(model);
        for (Pair<ModelBase, Double> p : list) {
            double d = count * p.getSecond(), c;
            ops.begin();
            c = add(p.getFirst(), rank, d, false, false, renew, isuse);
            ops.rollback();
            add(p.getFirst(), rank, d - c, true, false, renew, isuse);
            ret.add(new Pair<>(p.getFirst(), d - c));
            count = c / p.getSecond();
            if (isZero(count)) {
                rat.setFirst(p.getFirst());
                rat.setSecond(p.getSecond());
                return 0;
            }
        }
        return count;
    }

    double addPrivate(ModelBase model, double count, boolean force, boolean alterable, boolean renew, boolean isuse) {
        Keep keep = ops.peek(model);
        double a = keep.getChangeCount(0) + keep.getUsedCount(0) + count, b;
        if (a < 0) {
            if (keep.getAlteration() != null) {
                b = add(keep.getAlteration(), 0, a * keep.getAlterrate(), false, false, renew, isuse) / keep.getAlterrate();
                if (isuse) {
                    ops.putUsed(keep, keep.getAlteration(), a - b);
                }
                count -= a - b;
                a = b;
            }
            if (a < 0) {
                List<Pair<ModelBase, Double>> ret = new LinkedList<>();
                Pair<ModelBase, Double> rat = new Pair<>(null, null);
                b = needRep(ret, rat, model, 0, a, renew, isuse);
                count -= a - b;
                if (b < 0) {
                    return b;
                }
                a = b;
                if (isuse) {
                    for (Pair<ModelBase, Double> p : ret) {
                        ops.putUsed(keep, p.getFirst(), p.getSecond());
                    }
                }
                keep = ops.peek(model);
                if (!rat.getFirst().equals(keep.getAlteration())) {
                    if (keep.getAlteration() != null) {
                        changeByRep(keep.getAlteration(), keep, -keep.getAlterrate());
                    }
                    if (rat.getFirst() != null) {
                        changeByRep(rat.getFirst(), keep, rat.getSecond());
                    }
                    keep.setAlteration(rat.getFirst());
                    keep.setAlterrate(rat.getSecond());
                }
            }
        }
        if (a > 0) {
            keep = ops.peek(model);
            if (keep.getAlteration() != null) {
                changeByRep(keep.getAlteration(), keep, -keep.getAlterrate());
            }
            keep.setAlteration(null);
            if (isuse) {
                //卸载替换使用
                List<Pair<ModelBase, Double>> list = findReplace(model);
                for (Pair<ModelBase, Double> p : list) {
                    Double g = keep.getUsedAlter().get(p.getFirst());
                    if (g != null) {
                        double h = Math.max(-a * p.getSecond(), g), w;
                        assert h <= 0;
                        ops.begin();
                        w = add(p.getFirst(), 0, h, false, false, renew, true);
                        if (isZero(w)) {
                            ops.commit();
                        } else {
                            h -= w;
                            ops.rollback();
                            add(p.getFirst(), 0, h, false, false, renew, true);
                        }
                        keep = ops.peek(model);
                        ops.putUsed(keep, p.getFirst(), -h);
                        a += h * p.getSecond();
                        count += h * p.getSecond();
                        if (Eval.isZero(a)) {
                            break;
                        }
                    }
                }
            }
        }
        if (a > 0 || force || Eval.isZero(a)) {
            if (isuse) {
                keep.setUsedCount(0, keep.getUsedCount(0) + count);
            } else {
                keep.setChangeCount(0, keep.getChangeCount(0) + count);
            }
            return 0;
        } else {
            return a;
        }
    }

    double add(ModelBase model, int rank, double count,
            boolean force, boolean alterable, boolean renew, boolean isuse) {
        if (0 <= rank && rank <= 5) {
            //执行附属参数
            double a = 0, b = 0, c = 0;
            if (rank == 0) {
                Keep keep = new Keep();
                keep.copyFrom(ops.peek(model));
                c = addPrivate(model, count, force, alterable, renew, isuse);
                if (isuse) {
                    count = ops.peek(model).getUsedCount(0) - keep.getUsedCount(0);
                } else {
                    count = ops.peek(model).getChangeCount(0) - keep.getChangeCount(0);
                }
            } else {
                Keep keep = ops.peek(model);
                if (isuse) {
                    keep.setUsedCount(rank, keep.getUsedCount(rank) + count);
                } else {
                    keep.setChangeCount(rank, keep.getChangeCount(rank) + count);
                }
                if (keep.getAlteration() != null) {
                    b = add(keep.getAlteration(), rank, count * keep.getAlterrate(), false, false, renew, isuse) / keep.getAlterrate();
                }
            }
            if (model instanceof Obj) {
                Obj obj = (Obj) model;
                if (isuse) {
                    a = addObjUsed(obj, rank, count, force, alterable, renew);
                } else {
                    a = addObjChange(obj, rank, count, force, alterable, renew);
                }
            }
            return Eval.absMax(a, b, c);
        }
        return 0;
    }

    double addObjChange(Obj obj, int rank, double count,
            boolean force, boolean alterable, boolean renew) {
        double a = 0, b, c, d = 0;
        if (rank > 0 || renew) {
            if (count > 0) {
                a = addMapOneReturn(obj.getBuilt(), rank, count, force, alterable, renew, false);
            } else {
                a = -addMapOneReturn(obj.getDel(), rank, -count, force, alterable, renew, false);
            }
        }
        b = addMapOneReturn(obj.getDailyChg(), rank + 1, count, force, alterable, renew, false);
        c = addMapOneReturn(obj.getUsed(), rank, count, force, alterable, renew, true);
        return Eval.absMax(a, b, c);
    }

    double addObjUsed(Obj obj, int rank, double count,
            boolean force, boolean alterable, boolean renew) {
        return addMapOneReturn(obj.getUsedChg(), rank, count, force, alterable, renew, false);
    }

    void changeByRep(ModelBase dest, Keep src, double alterrate) {
        Keep keep = ops.peek(dest);
        for (int i = 1; i < 6; ++i) {
            keep.setChangeCount(i, keep.getChangeCount(i) + src.getChangeCount(i) * alterrate);
            keep.setUsedCount(i, keep.getUsedCount(i) + src.getUsedCount(i) * alterrate);
        }
    }

    /**
     *
     * @param obj
     * @param setted
     * @param mapped Map<单价, Pair<对象, 替代率>>
     */
    void findReplace(ModelBase model, Set<ModelBase> setted, SortedMap<Double, Pair<ModelBase, Double>> mapped, Double count) {
        if (!setted.contains(model)) {
            setted.add(model);
            Keep keep = ops.getOgn().getKeeps().get(model);
            if (keep != null) {
                mapped.put(keep.getPerPrice() * count, new Pair<>(model, count));
            }
            if (model instanceof Obj) {
                Obj obj = (Obj) model;
                for (Entry<ModelBase, Double> entry : obj.getRep().entrySet()) {
                    findReplace(entry.getKey(), setted, mapped, count * entry.getValue());
                }
            }
        }
    }

    List<Pair<ModelBase, Double>> findReplace(ModelBase model) {
        List<Pair<ModelBase, Double>> ret = new LinkedList<>();
        Set<ModelBase> setted = new HashSet<>();
        SortedMap<Double, Pair<ModelBase, Double>> mapped = new TreeMap<>();
        findReplace(model, setted, mapped, 1.0);
        for (Entry<Double, Pair<ModelBase, Double>> e : mapped.entrySet()) {
            if (!e.getValue().getFirst().equals(model)) {
                ret.add(e.getValue());
            }
        }
        return ret;
    }

    public boolean addCash(long cash) {
        return setCash(ops.getOgn().getCash() + cash);
    }

    public boolean setCash(long cash) {
        if (cash < 0) {
            return false;
        }
        return setCashForce(cash);
    }

    public boolean setCashForce(long cash) {
        ops.getOgn().setCash(cash);
        HibernateUtil.getSession().save(ops.getOgn());
        return true;
    }

    public OgnShow getShow() {
        Ogn ogn = ops.getOgn();
        OgnShow show = new OgnShow();
        show.setCash(ogn.getCash());
        show.setId(ogn.getId());
        show.setMonthCashTotal(ogn.getMonthCashTotal());
        show.setName(ogn.getName());
        return show;
    }

    public OgnKeepOps getOps() {
        return ops;
    }

    public void setOps(OgnKeepOps ops) {
        this.ops = ops;
    }
}
